package org.abettor.util.encoding;

import java.io.UnsupportedEncodingException;

/**
 * Base64编码解码工具
 * @author <a href="http://www.abettor.org/" target="_blank">abettor</a>
 *
 */
public abstract class EncodingBase64
{
	private static String baseString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	private static char[] base = baseString.toCharArray();
	private static char pad = '=';

	/**
	 * Base64编码操作
	 * @param src 需要进行编码的数据
	 * @return 执行失败返回null，否则返回编码后的数据
	 */
	public static String encode(byte[] src)
	{
		//	如果传入空数据则直接返回null
		if(src == null || src.length == 0)
		{
			return null;
		}

		//	计算编码后的长度
		int dstLen = src.length * 4 / 3;
		if(src.length * 4 % 3 > 0)
		{
			dstLen = (dstLen / 4 + 1) * 4;
		}

		//	以3byte为一组，逐组编码
		char[] dst = new char[dstLen];
		int srcPos = 0;
		int dstPos = 0;
		while(src.length - srcPos >= 3)
		{
			byte tmp = (byte) ((src[srcPos] >>> 2) & 0x3F);
			dst[dstPos] = base[tmp];

			tmp = (byte) ((src[srcPos] << 4) & 0x3F);
			tmp |= (src[srcPos + 1] >>> 4) & 0x0F;
			dst[dstPos + 1] = base[tmp];

			tmp = (byte) ((src[srcPos + 1] & 0x0F) << 2);
			tmp |= (src[srcPos + 2] >>> 6) & 0x03;
			dst[dstPos + 2] = base[tmp];

			tmp = (byte) (src[srcPos + 2] & 0x3F);
			dst[dstPos + 3] = base[tmp];

			srcPos += 3;
			dstPos += 4;
		}

		//	继续编码后续的数据
		if(src.length - srcPos == 1)
		{
			byte tmp = (byte) ((src[srcPos] >>> 2) & 0x3F);
			dst[dstPos] = base[tmp];

			tmp = (byte) ((src[srcPos] << 4) & 0x3F);
			dst[dstPos + 1] = base[tmp];

			dst[dstPos + 2] = dst[dstPos + 3] = pad;
		}
		else if(src.length - srcPos == 2)
		{
			byte tmp = (byte) ((src[srcPos] >>> 2) & 0x3F);
			dst[dstPos] = base[tmp];

			tmp = (byte) ((src[srcPos] << 4) & 0x3F);
			tmp |= (src[srcPos + 1] >>> 4) & 0x0F;
			dst[dstPos + 1] = base[tmp];

			tmp = (byte) ((src[srcPos + 1] & 0x0F) << 2);
			dst[dstPos + 2] = base[tmp];

			dst[dstPos + 3] = pad;
		}

		return new String(dst);
	}

	/**
	 * Base64解码操作
	 * @param src 需要进行解码的数据
	 * @return 执行失败返回null，否则返回解码后的数据
	 * @throws UnsupportedEncodingException 
	 */
	public static byte[] decode(String src) throws UnsupportedEncodingException
	{
		//	如果传入空数据则直接返回null
		if(src == null || src.length() == 0)
		{
			return null;
		}

		//	检查输入数据的长度
		int srcLen = src.length();
		if(srcLen % 4 > 0)
		{
			return null;
		}

		//	计算结果长度
		int dstLen = srcLen * 3 / 4;
		int index = src.indexOf(pad);
		String sub = src.substring(index);
		if(sub != null && sub.length() > 0)
		{
			int subLen = sub.length();
			if(subLen == 1)
			{
				//	1个pad，说明编码时补了1个char，也就是计算密文长度时余了2byte
				dstLen --;
			}
			else if(subLen == 2)
			{
				//	2个pad，说明编码时补了2个char，也就是计算密文长度时余了1byte
				dstLen -= 2;
			}
			else
			{
				//	不可能是其它值
				return null;
			}
		}

		//	整理src为数值
		byte[] src0 = new byte[srcLen];
		int i = 0;
		for(byte b : src.getBytes("ISO-8859-1"))
		{
			src0[i] = (byte) baseString.indexOf(b);
			i ++;
		}

		//	以4char（3byte）为一组，逐组解码
		byte[] dst = new byte[dstLen];
		int srcPos = 0;
		int dstPos = 0;
		while(dst.length - dstPos >= 3)
		{
			dst[dstPos] = (byte) ((src0[srcPos] << 2) & 0xFF);
			dst[dstPos] |= (src0[srcPos + 1] >>> 4) & 0x03;

			dst[dstPos + 1] = (byte) ((src0[srcPos + 1] << 4) & 0xFF);
			dst[dstPos + 1] |= (src0[srcPos + 2] >>> 2) & 0x0F;

			dst[dstPos + 2] = (byte) ((src0[srcPos + 2] << 6) & 0xFF);
			dst[dstPos + 2] |= src0[srcPos + 3];

			srcPos += 4;
			dstPos += 3;
		}

		//	最后将剩余的信息进行解码
		if(sub != null && sub.length() > 0)
		{
			int subLen = sub.length();
			if(subLen == 1)
			{
				//	还原最后2byte
				dst[dstPos] = (byte) ((src0[srcPos] << 2) & 0xFF);
				dst[dstPos] |= (src0[srcPos + 1] >>> 4) & 0x03;

				dst[dstPos + 1] = (byte) ((src0[srcPos + 1] << 4) & 0xFF);
				dst[dstPos + 1] |= (src0[srcPos + 2] >>> 2) & 0x0F;
			}
			else if(subLen == 2)
			{
				//	还原最后1byte
				dst[dstPos] = (byte) ((src0[srcPos] << 2) & 0xFF);
				dst[dstPos] |= (src0[srcPos + 1] >>> 4) & 0x03;
			}
		}

		return dst;
	}
}
